package com.maidada.codesharebackend.exception;

import cn.dev33.satoken.exception.NotLoginException;
import cn.dev33.satoken.exception.NotPermissionException;
import cn.dev33.satoken.exception.NotRoleException;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.fasterxml.jackson.databind.exc.InvalidFormatException;
import com.maidada.codesharebackend.common.CommonResult;
import lombok.extern.slf4j.Slf4j;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException;
import org.springframework.web.multipart.MaxUploadSizeExceededException;
import org.springframework.web.servlet.NoHandlerFoundException;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import java.sql.SQLException;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 全局异常处理器
 * 统一处理系统中的各类异常，返回标准的错误响应格式
 *
 * @author wulinxuan
 * @date 2025-06-24 17:02
 */
@Slf4j
@RestControllerAdvice
public class GlobalExceptionHandler {

    // ==================== 参数校验相关异常 ====================

    /**
     * 处理 SpringMVC 请求参数缺失
     * 例如说，接口上设置了 @RequestParam("xx") 参数，结果并未传递 xx 参数
     */
    @ExceptionHandler(value = MissingServletRequestParameterException.class)
    public CommonResult<?> missingServletRequestParameterExceptionHandler(MissingServletRequestParameterException e, HttpServletRequest request) {
        String errorMessage = String.format("请求参数缺失:%s", e.getParameterName());
        log.warn("[参数缺失异常] 请求路径: {}, 缺失参数: {}", request.getRequestURI(), e.getParameterName());
        return CommonResult.error(GlobalErrorCode.PARAMS_ERROR, errorMessage);
    }

    /**
     * 处理 SpringMVC 请求参数类型错误
     * 例如说，接口上设置了 @RequestParam("xx") 参数为 Integer，结果传递 xx 参数类型为 String
     */
    @ExceptionHandler(MethodArgumentTypeMismatchException.class)
    public CommonResult<?> methodArgumentTypeMismatchExceptionHandler(MethodArgumentTypeMismatchException e, HttpServletRequest request) {
        String errorMessage = String.format("请求参数类型错误:参数[%s]需要类型[%s]",
                e.getName(),
                e.getRequiredType() != null ? e.getRequiredType().getSimpleName() : "未知");
        log.warn("[参数类型错误] 请求路径: {}, 参数名: {}, 期望类型: {}, 实际值: {}",
                request.getRequestURI(), e.getName(), e.getRequiredType(), e.getValue());
        return CommonResult.error(GlobalErrorCode.PARAMS_ERROR, errorMessage);
    }

    /**
     * validation 参数校验异常 (@RequestBody 参数校验)
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public CommonResult<?> methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e, HttpServletRequest request) {
        String errorMessage = getValidationErrorMessage(e.getBindingResult());

        // 记录详细的校验错误信息
        List<String> errors = e.getBindingResult().getFieldErrors().stream()
                .map(error -> String.format("%s: %s", error.getField(), error.getDefaultMessage()))
                .collect(Collectors.toList());
        log.warn("[参数校验失败] 请求路径: {}, 校验错误: {}", request.getRequestURI(), errors);

        return CommonResult.error(GlobalErrorCode.PARAMS_ERROR, errorMessage);
    }

    /**
     * SpringMVC 参数绑定不正确，本质上也是通过 validation 校验 (@ModelAttribute 参数校验)
     */
    @ExceptionHandler(BindException.class)
    public CommonResult<?> bindExceptionHandler(BindException e, HttpServletRequest request) {
        String errorMessage = getValidationErrorMessage(e.getBindingResult());

        // 记录详细的校验错误信息
        List<String> errors = e.getBindingResult().getFieldErrors().stream()
                .map(error -> String.format("%s: %s", error.getField(), error.getDefaultMessage()))
                .collect(Collectors.toList());
        log.warn("[参数绑定失败] 请求路径: {}, 校验错误: {}", request.getRequestURI(), errors);

        return CommonResult.error(GlobalErrorCode.PARAMS_ERROR, errorMessage);
    }

    /**
     * 处理 SpringMVC 请求参数类型错误
     * 例如说，接口上设置了 @RequestBody实体中 xx 属性类型为 Integer，结果传递 xx 参数类型为 String
     */
    @ExceptionHandler(HttpMessageNotReadableException.class)
    public CommonResult<?> httpMessageNotReadableExceptionHandler(HttpMessageNotReadableException e, HttpServletRequest request) {
        if (e.getCause() instanceof InvalidFormatException) {
            InvalidFormatException invalidFormatException = (InvalidFormatException) e.getCause();
            String errorMessage = String.format("请求参数类型错误:值[%s]无法转换为目标类型", invalidFormatException.getValue());
            log.warn("[请求体解析失败] 请求路径: {}, 错误值: {}, 目标类型: {}",
                    request.getRequestURI(), invalidFormatException.getValue(), invalidFormatException.getTargetType());
            return CommonResult.error(GlobalErrorCode.PARAMS_ERROR, errorMessage);
        } else {
            log.warn("[请求体解析失败] 请求路径: {}, 错误信息: {}", request.getRequestURI(), e.getMessage());
            return CommonResult.error(GlobalErrorCode.PARAMS_ERROR, "请求体格式错误，请检查JSON格式");
        }
    }

    /**
     * 处理 Validator 校验不通过产生的异常 (方法参数校验)
     */
    @ExceptionHandler(value = ConstraintViolationException.class)
    public CommonResult<?> constraintViolationExceptionHandler(ConstraintViolationException e, HttpServletRequest request) {
        // 收集所有校验错误信息
        List<String> errors = e.getConstraintViolations().stream()
                .map(violation -> String.format("%s: %s", violation.getPropertyPath(), violation.getMessage()))
                .collect(Collectors.toList());

        String errorMessage = String.format("请求参数不正确:%s", errors.get(0));
        log.warn("[参数约束校验失败] 请求路径: {}, 校验错误: {}", request.getRequestURI(), errors);
        return CommonResult.error(GlobalErrorCode.PARAMS_ERROR, errorMessage);
    }

    // ==================== HTTP 请求相关异常 ====================

    /**
     * 处理 SpringMVC 请求地址不存在
     * 注意，它需要设置如下两个配置项：
     * 1. spring.mvc.throw-exception-if-no-handler-found 为 true
     * 2. spring.mvc.static-path-pattern 为 /statics/**
     */
    @ExceptionHandler(NoHandlerFoundException.class)
    public CommonResult<?> noHandlerFoundExceptionHandler(NoHandlerFoundException e) {
        String errorMessage = String.format("请求地址不存在:%s", e.getRequestURL());
        log.warn("[404错误] 请求方法: {}, 请求路径: {}", e.getHttpMethod(), e.getRequestURL());
        return CommonResult.error(GlobalErrorCode.NOT_FOUND_ERROR, errorMessage);
    }

    /**
     * 处理 SpringMVC 请求方法不正确
     * 例如说，A 接口的方法为 GET 方式，结果请求方法为 POST 方式，导致不匹配
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public CommonResult<?> httpRequestMethodNotSupportedExceptionHandler(HttpRequestMethodNotSupportedException e, HttpServletRequest request) {
        String errorMessage = String.format("请求方法不正确:当前方法[%s],支持方法%s",
                e.getMethod(),
                e.getSupportedHttpMethods());
        log.warn("[请求方法错误] 请求路径: {}, 当前方法: {}, 支持方法: {}",
                request.getRequestURI(), e.getMethod(), e.getSupportedHttpMethods());
        return CommonResult.error(GlobalErrorCode.METHOD_NOT_ALLOWED_ERROR, errorMessage);
    }

    /**
     * 处理 Content-Type 不支持异常
     */
    @ExceptionHandler(HttpMediaTypeNotSupportedException.class)
    public CommonResult<?> httpMediaTypeNotSupportedExceptionHandler(HttpMediaTypeNotSupportedException e, HttpServletRequest request) {
        String errorMessage = String.format("不支持的Content-Type:%s", e.getContentType());
        log.warn("[Content-Type不支持] 请求路径: {}, Content-Type: {}, 支持类型: {}",
                request.getRequestURI(), e.getContentType(), e.getSupportedMediaTypes());
        return CommonResult.error(GlobalErrorCode.PARAMS_ERROR, errorMessage);
    }

    /**
     * 处理文件上传大小超限异常
     */
    @ExceptionHandler(MaxUploadSizeExceededException.class)
    public CommonResult<?> maxUploadSizeExceededExceptionHandler(MaxUploadSizeExceededException e, HttpServletRequest request) {
        String errorMessage = "上传文件大小超出限制";
        log.warn("[文件上传超限] 请求路径: {}, 最大允许: {}", request.getRequestURI(), e.getMaxUploadSize());
        return CommonResult.error(GlobalErrorCode.PARAMS_ERROR, errorMessage);
    }

    // ==================== 认证授权相关异常 ====================

    /**
     * 拦截：未登录异常
     */
    @ExceptionHandler(NotLoginException.class)
    public CommonResult<?> notLoginExceptionHandler(NotLoginException e, HttpServletRequest request) {
        log.warn("[未登录异常] 请求路径: {}, 异常类型: {}", request.getRequestURI(), e.getType());
        return CommonResult.error(GlobalErrorCode.NOT_LOGIN_ERROR, "用户未登录，请先登录");
    }

    /**
     * 拦截：缺少权限异常
     */
    @ExceptionHandler(NotPermissionException.class)
    public CommonResult<?> notPermissionExceptionHandler(NotPermissionException e, HttpServletRequest request) {
        String msg = "缺少权限：" + e.getPermission();
        log.warn("[权限不足] 请求路径: {}, 缺少权限: {}", request.getRequestURI(), e.getPermission());
        return CommonResult.error(GlobalErrorCode.NO_AUTH_ERROR, msg);
    }

    /**
     * 拦截：缺少角色异常
     */
    @ExceptionHandler(NotRoleException.class)
    public CommonResult<?> notRoleExceptionHandler(NotRoleException e, HttpServletRequest request) {
        String msg = "缺少角色：" + e.getRole();
        log.warn("[角色不足] 请求路径: {}, 缺少角色: {}", request.getRequestURI(), e.getRole());
        return CommonResult.error(GlobalErrorCode.NO_AUTH_ERROR, msg);
    }

    // ==================== 数据库相关异常 ====================

    /**
     * 处理数据库唯一键冲突异常
     */
    @ExceptionHandler(DuplicateKeyException.class)
    public CommonResult<?> duplicateKeyExceptionHandler(DuplicateKeyException e, HttpServletRequest request) {
        log.error("[数据库唯一键冲突] 请求路径: {}", request.getRequestURI(), e);
        return CommonResult.error(GlobalErrorCode.DATA_ALREADY_EXISTS_ERROR, "数据已存在，请勿重复操作");
    }

    /**
     * 处理 SQL 异常
     */
    @ExceptionHandler(SQLException.class)
    public CommonResult<?> sqlExceptionHandler(SQLException e, HttpServletRequest request) {
        log.error("[SQL异常] 请求路径: {}, SQL状态: {}, 错误码: {}",
                request.getRequestURI(), e.getSQLState(), e.getErrorCode(), e);
        return CommonResult.error(GlobalErrorCode.SYSTEM_ERROR, "数据库操作异常");
    }

    /**
     * 处理数据访问异常
     */
    @ExceptionHandler(DataAccessException.class)
    public CommonResult<?> dataAccessExceptionHandler(DataAccessException e, HttpServletRequest request) {
        log.error("[数据访问异常] 请求路径: {}", request.getRequestURI(), e);
        return CommonResult.error(GlobalErrorCode.SYSTEM_ERROR, "数据访问异常");
    }

    // ==================== 业务异常 ====================

    /**
     * 处理业务异常 BusinessException
     */
    @ExceptionHandler(BusinessException.class)
    public CommonResult<?> businessExceptionHandler(BusinessException e, HttpServletRequest request) {
        log.warn("[业务异常] 请求路径: {}, 错误码: {}, 错误信息: {}",
                request.getRequestURI(), e.getCode(), e.getMessage());
        return CommonResult.error(e.getCode(), e.getMessage());
    }

    // ==================== 其他异常 ====================

    /**
     * 处理空指针异常
     */
    @ExceptionHandler(NullPointerException.class)
    public CommonResult<?> nullPointerExceptionHandler(NullPointerException e, HttpServletRequest request) {
        log.error("[空指针异常] 请求路径: {}", request.getRequestURI(), e);
        return CommonResult.error(GlobalErrorCode.SYSTEM_ERROR, "系统内部异常");
    }

    /**
     * 处理非法参数异常
     */
    @ExceptionHandler(IllegalArgumentException.class)
    public CommonResult<?> illegalArgumentExceptionHandler(IllegalArgumentException e, HttpServletRequest request) {
        log.warn("[非法参数异常] 请求路径: {}, 错误信息: {}", request.getRequestURI(), e.getMessage());
        return CommonResult.error(GlobalErrorCode.PARAMS_ERROR, e.getMessage());
    }

    /**
     * 处理非法状态异常
     */
    @ExceptionHandler(IllegalStateException.class)
    public CommonResult<?> illegalStateExceptionHandler(IllegalStateException e, HttpServletRequest request) {
        log.error("[非法状态异常] 请求路径: {}, 错误信息: {}", request.getRequestURI(), e.getMessage(), e);
        return CommonResult.error(GlobalErrorCode.SYSTEM_ERROR, "系统状态异常");
    }

    /**
     * 处理系统异常，兜底处理所有的一切
     */
    @ExceptionHandler(value = Exception.class)
    public CommonResult<?> defaultExceptionHandler(Exception e, HttpServletRequest request) {
        log.error("[未知异常] 请求路径: {}, 异常类型: {}, 错误信息: {}",
                request.getRequestURI(), e.getClass().getName(), e.getMessage(), e);
        // 生产环境不返回具体的异常信息，避免泄露敏感信息
        return CommonResult.error(GlobalErrorCode.SYSTEM_ERROR, "系统内部异常，请联系管理员");
    }

    // ==================== 私有辅助方法 ====================

    /**
     * 从 BindingResult 中提取校验错误信息
     *
     * @param bindingResult 绑定结果
     * @return 错误信息
     */
    private String getValidationErrorMessage(org.springframework.validation.BindingResult bindingResult) {
        FieldError fieldError = bindingResult.getFieldError();
        String errorMessage;

        if (fieldError == null) {
            List<ObjectError> allErrors = bindingResult.getAllErrors();
            if (CollUtil.isNotEmpty(allErrors)) {
                errorMessage = allErrors.get(0).getDefaultMessage();
            } else {
                errorMessage = "参数校验失败";
            }
        } else {
            errorMessage = fieldError.getDefaultMessage();
        }

        if (StrUtil.isEmpty(errorMessage)) {
            errorMessage = "参数校验失败";
        }

        return String.format("请求参数不正确:%s", errorMessage);
    }
}
